package cn.gengar.config;

import cn.gengar.types.annotations.DCCValue;
import cn.gengar.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopProxyUtils;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 基于 Redis 实现动态配置中心
 */
@Slf4j
@Configuration
public class DCCValueBeanFactory implements BeanPostProcessor {

    private static final String BASE_CONFIG_PATH = "group_buy_market_dcc_";

    private final RedissonClient redissonClient;

    private final Map<String, Object> dccObject = new HashMap<>();

    public DCCValueBeanFactory(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }

    @Bean("dccTopic")
    public RTopic dccRedisTopicListener(RedissonClient redissonClient) {
        RTopic topic = redissonClient.getTopic("group_buy_market_dcc");
        // 添加监听
        topic.addListener(String.class, (channel, msg) -> {
            String[] split = msg.split(Constants.SPLIT);
            String key = BASE_CONFIG_PATH.concat(split[0]);
            String value = split[1];

            RBucket<String> bucket = redissonClient.getBucket(key);
            if (!bucket.isExists()) {
                return;
            }

            bucket.set(value);
            Object objBean = dccObject.get(key);
            if (Objects.isNull(objBean)) {
                return;
            }

            Class<?> objBeanClass = objBean.getClass();
            if (AopUtils.isAopProxy(objBeanClass)) {
                objBeanClass = AopUtils.getTargetClass(objBeanClass);
            }
            try {
                Field field = objBeanClass.getDeclaredField(split[0]);
                field.setAccessible(true);
                field.set(objBean, value);
                field.setAccessible(false);

                log.info("DCC 节点监听，动态设置值 {} {}", key, value);
            }catch (Exception e) {
                throw new RuntimeException(e);
            }

        });
        return topic;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        // 注意；增加 AOP 代理后，获得类的方式要通过 AopProxyUtils.getTargetClass(bean); 不能直接 bean.class 因为代理后类的结构发生变化，这样不能获得到自己的自定义注解了。
        Class<?> targetBeanClass = bean.getClass();
        Object targetBeanObject = bean;

        if (AopUtils.isAopProxy(targetBeanClass)) {
            targetBeanClass = AopUtils.getTargetClass(bean);
            targetBeanObject = AopProxyUtils.getSingletonTarget(bean);
        }

        Field[] fields = targetBeanClass.getDeclaredFields();
        for (Field field : fields) {
            if (!field.isAnnotationPresent(DCCValue.class)) {
                continue;
            }

            DCCValue dccValue = field.getAnnotation(DCCValue.class);
            String value = dccValue.value();
            if (StringUtils.isBlank(value)) {
                throw new RuntimeException(field.getName() + " @DCCValue is not config value config case 「isSwitch/isSwitch:1」");
            }

            String[] split = value.split(":");
            // 拼接Key
            String key = BASE_CONFIG_PATH.concat(split[0]);

            String defaultValue = split.length == 2 ? split[1] : null;

            try {
                String setValue = defaultValue;
                if (StringUtils.isBlank(defaultValue)) {
                    throw new RuntimeException("dcc config error " + key + " is not null - 请配置默认值！");
                }
                RBucket<String> bucket = redissonClient.getBucket(key);

                boolean exists = bucket.isExists();
               if (!exists) {
                   bucket.set(defaultValue);
               } else {
                   setValue = bucket.get();
               }
                field.setAccessible(true);
                field.set(targetBeanObject, setValue);
                field.setAccessible(false);

            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }

            dccObject.put(key, targetBeanObject);
        }
        return bean;
    }
}
